package thread;

// 演示线程安全问题

// 方法三：
class Locker {

}

class Counter {
    public int count = 0;
    /*// 因为存在线程不安全问题，进行修改时：在该方法上加上synchronized关键字！！ （也就是保证修改操作原子性）
    // 进入方法自然加锁，方法结束自然解锁
    public synchronized void increase() {
        count++;
    }*/

    // 方法二：
    // public Object locker = new Object();

    // 方法三：
    // public static Locker locker = new Locker();

    // 方法四：static修饰（那就说明只有一份！！）
    private static Object locker = new Object();

    // 给代码块进行加锁
    public synchronized void increase() {
        // 方法一：
       /* synchronized (this) { // ()中是锁对象
            count++;
        }*/

        // 方法二、三、四：
        /*synchronized (locker) { // ()中是锁对象
            count++;
        }*/

        // 方法五：一个针对locker加锁 一个针对this加锁
        // 输出结果是10000 与预期结果相符——
        /*synchronized (locker) { // ()中是锁对象
            count++;
        }*/

        // 方法六：此处的锁对象就变成了一个“类对象”（反射）
        // “类对象”中保存了该类更加详细的信息（包括隐藏的方法、属性）
        // “类对象”来自.class文件，进而来自.java文件
        // 类对象在JVM中只有一个！！如果多个线程同时针对类对象加锁，就会存在锁竞争！
        synchronized (Counter.class) { // ()中是锁对象
            count++;
        }
    }

    // 再给一个方法不加锁
    // 看一个线程加锁一个线程不加锁最后的效果：
    // 此时结果不符合预期，线程不安全！！
    public void increase2() {
        //count++;

        // 方法五（两个方法）
        synchronized (this) {
            count++;
        }
    }
}

public class Demo8 {
    private static Counter counter = new Counter();

    private static Counter counter2 = new Counter();

    public static void main(String[] args) throws InterruptedException {
        // 两个线程，让每个线程均对count变量自增5w次
        // 预期结果 10w

        // 线程中，increase方法是加锁 自增 解锁（相当于串行）， 涉及到锁竞争
        // 而外面的for循环并没有被加锁，是并行执行的
        // 所以：此时 该代码比两个循环串行快，但是比不加锁的并发执行慢

        // 如果把for循环也写到了加锁的代码里，那此时就和完全串行的效果一样了
        Thread t1 = new Thread(() -> {
            for (int i=0; i<5_0000; i++) {
                // 针对 静态locker加锁
                counter.increase();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i=0; i<5_0000; i++) {
                // 针对this本身加锁
                 counter.increase();
                //counter.increase2();
                //counter2.increase();
                // 此时的counter和counter2虽然是不同的实例对象，但是increase是同一份，
                // 因为是static修饰的Object，则此时拿到的locker是同一个，所以increase也是同一个（但是是原子操作）
                // 所以：这里的线程其实是存在锁竞争的，两个线程抢同一个锁
            }
        });
        // 方法五的两个线程是针对不同的对象加锁，本来就不存在锁竞争关系，就不存在 “锁整个类”

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        // main线程等待t1、t2执行完成

        System.out.println("counter: " + counter.count);
        // 线程不安全：输出结果于预期结果不一致，况且每次运行结果都不一样

        // 上述问题是怎么出现的呢？

        // 实际上，进行count++操作，底层是三条指令在CPU上完成的
        // ① 把内存数据读取到CPU寄存器中 load
        // ② 把CPU寄存器中的数据进行+1  add
        // ③ 把寄存器中的值写回到内存  save

        // 当前是两个线程修改同一个变量，并且 每次修改是三个步骤都要完成的（不是原子性），而 线程之间的调度顺序又是不确定的
        // 所以：两个线程在真正执行这些操作的时候，就有可能出现多种执行顺序（其中只有少数一部分是没有bug的）

        // 整个线程执行过程中执行的顺序都是随机的：
        // 由于在调度过程中，出现“串行执行”（也就是正确结果）的两种情况的次数 和其他情况的执行次数时不确定的
        // 因此得到的结果的值就是不确定的
        // 结果范围：（考虑极端情况）
        // ① 如果两个线程之间的调度全是 “串行执行”， 结果就是10w
        // ② 如果两个线程之间的调度全是 其他情况，一次“串行”都没有，结果就是5w
        // 所以：最终结果在5w-10w之间 （但是也有可能出现<5w)
        // 如t1尝试加一次，t2尝试加2次，则先执行完t2后再执行完t1，内存中的结果其实就会变成t1中的1！！！

    }
}
